/**
 *
 */
package com.honeybees.framework.common.util.rsa;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <dl>
 * <dt><b> RSA加密、解密 工具 </b></dt>
 * <p>
 * <dd>plugins/jsencrypt/jsencrypt.min.js</dd>
 * </dl>
 * <p>
 * Copyright (C) All rights reserved.
 * </p>
 *
 * @author 李远明
 * @author wangshupeng@symdata.cn
 * @since 2016-08-18 10:35:51
 */
public final class RSAEncryptUtils {

    private static final Logger logger = LoggerFactory.getLogger(RSAEncryptUtils.class);

    /**
     * 公钥文件名
     * <p>
     * pem: Privacy Enhanced Mail（增强的私密电子邮件）
     */
    private static final String PUB_KEY_FILE = "/public_key.pem";
    /**
     * 私钥文件名
     */
    private static final String PRI_KEY_FILE = "/private_key.pem";

    /**
     * 算法名称
     */
    private static final String RSA = "RSA";
    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;
    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 随机生成密钥对
     *
     * @return
     * @throws Exception
     */
    public static String genKeyPair(String filePath) throws Exception {
        // KeyPairGenerator类用于生成公钥和私钥对，基于RSA算法生成对象
        KeyPairGenerator keyPairGen = null;
        try {
            keyPairGen = KeyPairGenerator.getInstance(RSA);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception(e.getMessage());
        }
        // 初始化密钥对生成器，密钥大小为96-1024位
        keyPairGen.initialize(1024, new SecureRandom());

        // 生成一个密钥对，保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();

        // 公钥
        RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();

        // 私钥
        RSAPrivateKey priKey = (RSAPrivateKey) keyPair.getPrivate();
        try {
            // 公钥字符串
            String pubKeyText = Base64.encode(pubKey.getEncoded());

            // 私钥字符串
            String priKeyText = Base64.encode(priKey.getEncoded());

            // 将密钥对写入到文件
            String pubFile = filePath + PUB_KEY_FILE;
            write(pubFile, pubKeyText);

            String priFile = filePath + PRI_KEY_FILE;
            return write(priFile, priKeyText);
        } catch (Exception e) {
            logger.error("genKeyPair error", e);
            throw e;
        }
    }

    /**
     * <dl>
     * <dt><b> nio 读文件 </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @param path 路径
     * @return 文件内容
     * @throws IOException 可能发生的异常
     * @author 李远明
     * @version 2016-08-27 23:18:38
     */
    private static String read(String path) throws IOException {
        Path p = Paths.get(path);
        File file = p.toFile();

        RandomAccessFile aFile = new RandomAccessFile(file, "r"); // rw
        FileChannel readChannel = aFile.getChannel();

        int size = (int) readChannel.size();

        ByteBuffer buf = ByteBuffer.allocate(size);
        byte[] dst = new byte[size];
        while (true) {
            buf.clear();
            int len = readChannel.read(buf);
            if (len == -1) {
                break;
            }
            buf.flip();
            while (buf.hasRemaining()) {
                buf.get(dst);
            }
        }
        aFile.close();
        return new String(dst);
    }

    /**
     * <dl>
     * <dt><b> nio 写文件 </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @param path 路径
     * @param data 数据
     * @throws IOException 可能发生的异常
     * @author 李远明
     * @version 2016-08-28 10:58:30
     */
    private static String write(String path, String data) throws IOException {
        Path p = Paths.get(path);
        File file = p.toFile();
        File parentFile = file.getParentFile();
        if (!parentFile.exists()) {
            if (!parentFile.mkdir()) {
                String msg = "该目录不存在或不能创建，请更换：" + path;
                throw new IOException(msg);
            }
        }

        RandomAccessFile aFile = new RandomAccessFile(file, "rw");
        FileChannel writeChannel = aFile.getChannel();

        byte[] bytes = data.getBytes();
        ByteBuffer buf = ByteBuffer.wrap(bytes);

        writeChannel.write(buf);
        aFile.close();

        return parentFile.getPath();
    }

    /**
     * 从文件中读取公钥
     *
     * @param path 公钥文件路径
     * @throws Exception 加载公钥时产生的异常
     */
    public static String readPublicKey(String path) throws Exception {
        String pubFile = path + PUB_KEY_FILE;
        try {
            return read(pubFile);
        } catch (IOException e) {
            throw new Exception("公钥数据流读取错误");
        } catch (NullPointerException e) {
            throw new Exception("公钥输入流为空");
        }
    }

    /**
     * 根据公钥字符串创建公钥
     *
     * @param base64Text 公钥数据字符串
     * @throws Exception 加载公钥时产生的异常
     * @see #createPrivateKey(String)
     */
    public static RSAPublicKey createPublicKey(String base64Text) throws Exception {
        try {
            byte[] key = Base64.decode(base64Text);
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key);
            return (RSAPublicKey) keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("公钥非法");
        } catch (NullPointerException e) {
            throw new Exception("公钥数据为空");
        }
    }

    /**
     * 从文件中加载私钥
     *
     * @param filePath 私钥文件路径
     * @return 私钥字符串
     * @throws Exception
     */
    public static String readPrivateKey(String filePath) throws Exception {
        String priFile = filePath + PRI_KEY_FILE;
        try {
            return read(priFile);
        } catch (IOException e) {
            throw new Exception("私钥数据读取错误");
        } catch (NullPointerException e) {
            throw new Exception("私钥输入流为空");
        }
    }

    /**
     * 根据私钥字符串创建公钥
     *
     * @param base64Text 私钥数据字符串
     * @throws Exception 加载公钥时产生的异常
     * @see #createPublicKey(String)
     */
    public static RSAPrivateKey createPrivateKey(String base64Text) throws Exception {
        try {
            byte[] key = Base64.decode(base64Text);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("私钥非法");
        } catch (NullPointerException e) {
            throw new Exception("私钥数据为空");
        }
    }

    /**
     * 公钥加密
     *
     * @param plainData 明文数据
     * @param publicKey 公钥
     * @return
     * @throws Exception 加密过程中的异常信息
     * @see #decrypt(byte[], RSAPrivateKey)
     * @see #encrypt(String, String)
     */
    public static byte[] encrypt(byte[] plainData, RSAPublicKey publicKey) throws Exception {
        if (plainData == null || plainData.length == 0) {
            throw new Exception("明文数据不能为空");
        }
        if (publicKey == null) {
            throw new Exception("加密公钥不能为空");
        }
        return commonEncrypt(plainData, publicKey); // 合并 2017-03-14 13:39:24
    }

    /**
     * 加密
     * <p>
     * 公钥加密与私钥加密合并
     * </p>
     *
     * @param plainData 明文数据
     * @param key       密钥
     * @return 密文数据
     * @throws Exception 异常
     * @version 2017-03-14 13:37:59
     * @see #commonDecrypt(byte[], Key)
     */
    private static byte[] commonEncrypt(byte[] plainData, Key key) throws Exception {
        Cipher cipher;
        try {
            cipher = Cipher.getInstance(RSA); // 使用RSA算法
            // cipher = Cipher.getInstance(RSA, new
            // org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher.init(Cipher.ENCRYPT_MODE, key); // 加密

            // ---------------加密---------------
            return doFinal(cipher, plainData, MAX_ENCRYPT_BLOCK); // encryptedData
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此加密算法");
        } catch (NoSuchPaddingException e) {
            throw new Exception("加密失败：NoSuchPaddingException");
        } catch (InvalidKeyException e) {
            throw new Exception("加密密钥非法，请检查");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("明文长度非法");
        } catch (BadPaddingException e) {
            throw new Exception("明文数据已损坏");
        }
    }

    /**
     * 加密、解密运算
     *
     * @param cipher    Cipher
     * @param data      明文/密文字节数据
     * @param blockSize 块大小
     * @return 运算结果
     * @throws BadPaddingException       异常
     * @throws IllegalBlockSizeException 异常
     * @throws IOException               异常
     * @author 李远明
     * @version 2017-03-14 16:18:22
     */
    private static byte[] doFinal(Cipher cipher, byte[] data, int blockSize)
            throws IllegalBlockSizeException, BadPaddingException, IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int length = data.length;
        int offset = 0;
        byte[] cache;
        int i = 0;

        // ---------------对数据进行分段加密、解密运算---------------
        while (length - offset > 0) {
            if (length - offset > blockSize) {
                cache = cipher.doFinal(data, offset, blockSize);
            } else {
                cache = cipher.doFinal(data, offset, length - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * blockSize;
        }
        byte[] outData = out.toByteArray();
        out.close();
        return outData;
    }

    /**
     * <dl>
     * <dt><b> RSA 公钥加密 </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @param plainData 原始明文数据
     * @param publicKey 经过Base64编码的公钥
     * @return 经过Base64编码的密文
     * @throws Exception 可能发生的异常
     * @author 李远明
     * @version 2016-08-27 22:21:56
     * @see #encrypt(byte[], RSAPublicKey)
     * @see #decrypt(String, String)
     */
    public static String encrypt(String plainData, String publicKey) throws Exception {
        if (plainData == null || plainData.length() == 0) {
            throw new Exception("明文数据不能为空");
        }
        if (publicKey == null || publicKey.length() == 0) {
            throw new Exception("加密公钥不能为空");
        }
        byte[] data = plainData.getBytes(StandardCharsets.UTF_8);
        RSAPublicKey key = createPublicKey(publicKey);
        byte[] encryptedData = encrypt(data, key);
        return Base64.encode(encryptedData);
    }

    /**
     * RSA 私钥加密
     *
     * @param plainData  明文数据
     * @param privateKey 私钥
     * @return
     * @throws Exception 加密过程中的异常信息
     */
    private static byte[] encrypt(byte[] plainData, RSAPrivateKey privateKey) throws Exception {
        if (plainData == null || plainData.length == 0) {
            throw new Exception("明文数据不能为空");
        }
        if (privateKey == null) {
            throw new Exception("加密私钥不能为空");
        }
        return commonEncrypt(plainData, privateKey); // 2017-03-14 13:39:57
    }

    /**
     * RSA 私钥解密
     *
     * @param cipherData 密文数据
     * @param privateKey 私钥
     * @return 明文
     * @throws Exception 解密过程中的异常信息
     * @see #encrypt(byte[], RSAPublicKey)
     * @see #decrypt(String, String)
     */
    public static byte[] decrypt(byte[] cipherData, RSAPrivateKey privateKey) throws Exception {
        if (cipherData == null || cipherData.length == 0) {
            throw new Exception("密文数据不能为空");
        }
        if (privateKey == null) {
            throw new Exception("解密私钥不能为空");
        }
        return commonDecrypt(cipherData, privateKey); // 合并 2017-03-14 13:49:36
    }

    /**
     * 解密
     * <p>
     * 私钥解密、公钥解密合并
     * </p>
     *
     * @param cipherData 密文数据
     * @param key        密钥
     * @return 明文
     * @throws Exception 异常
     * @see #commonEncrypt(byte[], Key)
     */
    private static byte[] commonDecrypt(byte[] cipherData, Key key) throws Exception {
        Cipher cipher;
        try {
            cipher = Cipher.getInstance(RSA); // 使用RSA算法
            // cipher = Cipher.getInstance(RSA, new
            // org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher.init(Cipher.DECRYPT_MODE, key); // 解密

            // ---------------解密---------------
            return doFinal(cipher, cipherData, MAX_DECRYPT_BLOCK); // decryptedData
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此解密算法");
        } catch (NoSuchPaddingException e) {
            throw new Exception("解密失败：NoSuchPaddingException");
        } catch (InvalidKeyException e) {
            throw new Exception("解密私钥非法，请检查");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("密文长度非法");
        } catch (BadPaddingException e) {
            throw new Exception("密文数据已损坏");
        }
    }

    /**
     * <dl>
     * <dt><b> RSA 私钥解密 </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @param cipherData 经过Base64编码的密文
     * @param privateKey 经过Base64编码的私钥
     * @return 解密之后的明文
     * @throws Exception 可能发生的异常
     * @author 李远明
     * @version 2016-08-27 22:37:09
     * @see #decrypt(byte[], RSAPrivateKey)
     * @see #encrypt(String, String)
     */
    public static String decrypt(String cipherData, String privateKey) throws Exception {
        if (cipherData == null || cipherData.length() == 0) {
            throw new Exception("密文数据不能为空");
        }
        if (privateKey == null || privateKey.length() == 0) {
            throw new Exception("解密私钥不能为空");
        }
        byte[] data = Base64.decode(cipherData);
        RSAPrivateKey key = createPrivateKey(privateKey);
        byte[] decryptedData = decrypt(data, key);
        return new String(decryptedData, StandardCharsets.UTF_8);
    }

    /**
     * RSA 公钥解密
     *
     * @param cipherData 密文数据
     * @param publicKey  公钥
     * @return 明文
     * @throws Exception 解密过程中的异常信息
     */
    private static byte[] decrypt(byte[] cipherData, RSAPublicKey publicKey) throws Exception {
        if (cipherData == null) {
            throw new Exception("密文数据不能为空");
        }
        if (publicKey == null) {
            throw new Exception("解密公钥不能为空");
        }
        return commonDecrypt(cipherData, publicKey);
    }

    /**
     * 计算字符串的SHA256哈希值
     * <p>
     * 用于计算公钥的SHA256值，再从数据库中查找私钥
     * </p>
     * t_mgmt_key
     *
     * @param text 公钥
     * @return SHA256值
     * @version 2017-03-14 20:29:38
     */
    public static String sha256(String text) {
        if (text == null || text.isEmpty()) {
            return null;
        }
        String algorithm = "SHA-256"; // SHA-512
        MessageDigest md;
        try {
            md = MessageDigest.getInstance(algorithm);
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
        byte[] input = text.getBytes();
        md.update(input, 0, input.length);
        byte[] out = md.digest();

        byte[] encoded = Hex.encode(out);
        return new String(encoded);
    }

    /**
     * 生成公钥、私钥
     *
     * @param args 参数
     * @throws Exception
     */
    public static void main1(String[] args) throws Exception {
        String path = "D:/key";
        genKeyPair(path);
    }

    /**
     * 公钥加密、私钥解密
     *
     * @param args 参数
     * @throws Exception 异常
     */
    public static void main(String[] args) throws Exception {
        String path = "D:/key";

        // 密码明文
        String password = "fjkK3;89Hds,slXs0wAg"; // 20位

        String publicKeyStr = readPublicKey(path);
        RSAPublicKey publicKey = createPublicKey(publicKeyStr);

        String privateKeyStr = readPrivateKey(path);
        RSAPrivateKey privateKey = createPrivateKey(privateKeyStr);

        // RSA 公钥加密
        byte[] encryptData = encrypt(password.getBytes(StandardCharsets.UTF_8), publicKey);
        String cipherData = Base64.encode(encryptData);
        System.out.println(cipherData);

        // RSA 私钥解密
        byte[] decryptData = decrypt(Base64.decode(cipherData), privateKey);
        System.out.println(new String(decryptData, StandardCharsets.UTF_8));

        // { // 用新方法加密、解密 2016-08-27 22:51
        // String a = encrypt(password, publicKeyStr);
        // System.out.println(a);
        //
        // String b = decrypt(a, privateKeyStr);
        // System.out.println(b);
        // }

        // // 签名及认证
        // String sign = SignatureUtils.sign(text, privateKeyStr);
        // boolean b = SignatureUtils.verifySignature(text, sign, publicKeyStr);
        // System.out.println(b);
    }

    /**
     * 公钥解密、私钥加密
     *
     * @param args 参数
     * @throws Exception 异常
     * @version 2017-03-14 13:19:00
     */
    public static void main3(String[] args) throws Exception {
        String path = "D:/key";

        // 密码明文
        String password = "000000"; // 6位

        String publicKeyStr = readPublicKey(path);
        RSAPublicKey publicKey = createPublicKey(publicKeyStr);

        String privateKeyStr = readPrivateKey(path);
        RSAPrivateKey privateKey = createPrivateKey(privateKeyStr);

        // RSA 私钥加密
        byte[] encryptData = encrypt(password.getBytes(StandardCharsets.UTF_8), privateKey);
        String cipherData = Base64.encode(encryptData);
        System.out.println(cipherData);

        // RSA 公钥解密
        byte[] decryptData = decrypt(Base64.decode(cipherData), publicKey);
        System.out.println(new String(decryptData, StandardCharsets.UTF_8));
    }
}
